
#include <stdlib.h>
#include <sys/time.h>
#include <fcntl.h>
#include "dictionary.h"
#include "iniparser.h"
#include "util_process_state.h"

// 默认值
#define DEFAULT_INT (0)
#define DEFAULT_STR "NONE"
#define DEFAULT_SECTION "state"

// 作为结尾使用
#define KEY_END_IDX 0xffff

typedef struct
{
    unsigned int val_int;      // 整型值
    char val_str[MAX_BUF_LEN]; // 字符串值
    char type;                 // 类型
    char *key;                 // 名字
} state_item_t;

// item不同类型，处理不同
typedef enum
{
    ITEM_TYPE_INT = 1, // 无符号整形
    ITEM_TYPE_STR,     // 字符串
    ITEM_TYPE_END,     // 序列结束类型
} item_type_e;

#define DEFINE_ITEM(index, t) \
    {                         \
        .key = #index,        \
        .type = t,            \
    }

// 将类型(int or str) + 名字 + 索引(KEY_X)建立关系
// 临时程序状态，掉电消息，保存在内存中
static state_item_t g_tmp_states[] =
    {
        DEFINE_ITEM(KEY_RUN_SECOND, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_RUN_TOTAL_SECOND, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_RUN_EXCEPT, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_RUN_VERSION, ITEM_TYPE_STR),
        DEFINE_ITEM(KEY_RUN_FIRST_TIME, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_RUN_CURR_TIME, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_RUN_RECORD_TIME, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_END_IDX, ITEM_TYPE_END),
};
// 需保存的程序状态，掉电后不消失，保存在文件中
static state_item_t g_save_states[] =
    {
        DEFINE_ITEM(KEY_TOTAL_SECOND, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_TOTAL_EXCEPT, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_TOTAL_BOOTCNT, ITEM_TYPE_INT),

        DEFINE_ITEM(KEY_LAST_EXCEPT_BOOTCNT, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_LAST_EXCEPT_VERSION, ITEM_TYPE_STR),
        DEFINE_ITEM(KEY_LAST_EXCEPT_TOTAL, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_LAST_EXCEPT_TIME, ITEM_TYPE_INT),
        DEFINE_ITEM(KEY_END_IDX, ITEM_TYPE_END),
};

// 通过file_e来索引
static char *g_filenames[] = {FILE_TMP_STATE, FILE_SAVE_STATE};
static state_item_t *g_states[] = {g_tmp_states, g_save_states};

// 设置默认打印函数，可以传入外部打印函数，以便接入到外部日志系统
static print_func_t g_print_func = printf;

// 设置配置文件某个关键字的值
// value可以兼容字符串和整形
static int proc_state_set(file_e file_id, int key, void *value)
{
    char val_str[MAX_BUF_LEN], key_str[MAX_BUF_LEN];
    dictionary *p_ini = NULL;
    state_item_t *p_state_items = NULL;
    FILE *fp;

    if (file_id >= FILE_MAX)
    {
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }
    p_ini = iniparser_load(g_filenames[file_id]);
    if (p_ini == NULL)
    {
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }
    p_state_items = g_states[file_id];
    sprintf(key_str, "%s:%s", DEFAULT_SECTION, p_state_items[key].key);
    switch (p_state_items[key].type)
    {
    case ITEM_TYPE_INT:
        p_state_items[key].val_int = (unsigned int)value;
        sprintf(val_str, "%u", (unsigned int)value);
        iniparser_set(p_ini, key_str, val_str);
        break;
    case ITEM_TYPE_STR:
        strcpy(p_state_items[key].val_str, (char *)value);
        iniparser_set(p_ini, key_str, (char *)value);
        break;
    default:
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        break;
    }
    fp = fopen(g_filenames[file_id], "w");
    if (fp == NULL)
    {
        iniparser_freedict(p_ini);
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }
    iniparser_dump_ini(p_ini, fp);
    fclose(fp);
    iniparser_freedict(p_ini);

    return 0;
}

static int proc_state_increase(file_e file_id, int key, int step)
{
    char val_str[MAX_BUF_LEN], key_str[MAX_BUF_LEN];
    dictionary *p_ini = NULL;
    state_item_t *p_state_items = NULL;
    FILE *fp = NULL;
    int ret;

    if (file_id >= FILE_MAX)
    {
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }

    p_ini = iniparser_load(g_filenames[file_id]);
    if (p_ini == NULL)
    {
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }

    p_state_items = g_states[file_id];

    sprintf(key_str, "%s:%s", DEFAULT_SECTION, p_state_items[key].key);
    p_state_items[key].val_int += step;
    sprintf(val_str, "%u", p_state_items[key].val_int);
    ret = iniparser_set(p_ini, key_str, val_str);
    if (ret != 0)
    {
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }
    fp = fopen(g_filenames[file_id], "w");
    if (fp == NULL)
    {
        iniparser_freedict(p_ini);
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }
    iniparser_dump_ini(p_ini, fp);
    fclose(fp);
    iniparser_freedict(p_ini);
    return 0;
}

static int proc_state_create_file(file_e file_id)
{
    int i = 0;
    char val_str[MAX_BUF_LEN];
    state_item_t *p_state_items = NULL;
    FILE *fp = NULL;
    if (file_id >= FILE_MAX)
    {
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }
    p_state_items = g_states[file_id];
    if (access(g_filenames[file_id], R_OK) != 0)
    {
        fp = fopen(g_filenames[file_id], "w");
        if (fp == NULL)
        {
            g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
            return -1;
        }

        memset(val_str, 0x00, sizeof(val_str));
        sprintf(val_str, "[%s]\n", DEFAULT_SECTION);
        fprintf(fp, "%s", val_str);
        do
        {
            memset(val_str, 0x00, sizeof(val_str));
            switch (p_state_items[i].type)
            {
            case ITEM_TYPE_INT:
                p_state_items[i].val_int = DEFAULT_INT;
                snprintf(val_str, sizeof(val_str), "%s=%u\n", p_state_items[i].key, p_state_items[i].val_int);
                break;
            case ITEM_TYPE_STR:
                strcpy(p_state_items[i].val_str, DEFAULT_STR);
                snprintf(val_str, sizeof(val_str), "%s=%s\n", p_state_items[i].key, p_state_items[i].val_str);
                break;
            }
            fprintf(fp, "%s", val_str);
            i++;
        } while (p_state_items[i].type != ITEM_TYPE_END);
        fclose(fp);
    }
    return 0;
}

static int proc_state_load_file(file_e file_id)
{
    int i = 0;
    dictionary *p_ini = NULL;
    state_item_t *p_state_items = NULL;
    char *val_str, key_str[MAX_BUF_LEN];
    if (file_id >= FILE_MAX)
    {
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }
    p_state_items = g_states[file_id];
    p_ini = iniparser_load(g_filenames[file_id]);
    if (p_ini == NULL)
    {
        g_print_func("%s %d failed\n", __FUNCTION__, __LINE__);
        return -1;
    }

    do
    {
        sprintf(key_str, "%s:%s", DEFAULT_SECTION, p_state_items[i].key);
        switch (p_state_items[i].type)
        {
        case ITEM_TYPE_INT:
            p_state_items[i].val_int = iniparser_getint(p_ini, key_str, DEFAULT_INT);
            break;
        case ITEM_TYPE_STR:
            val_str = (char *)iniparser_getstring(p_ini, key_str, DEFAULT_STR);
            strcpy(p_state_items[i].val_str, val_str);
            break;
        }
        i++;
    } while (p_state_items[i].type != ITEM_TYPE_END);
    iniparser_freedict(p_ini);
    return 0;
}

// 放在main函数比较靠前的位置调用
int proc_state_bootup(const char *version, print_func_t print)
{
    int ret;
    struct timeval curr;

    // 使用外部传入的打印函数
    if (print != NULL)
    {
        g_print_func = print;
    }

    gettimeofday(&curr, NULL);
    // 先查询程序临时状态
    if (access(g_filenames[FILE_TMP_IDX], R_OK) != 0)
    {
        // 文件不存在则创建文件
        ret = proc_state_create_file(FILE_TMP_IDX);
        if (ret < 0)
        {
            return -1;
        }
        // 记录下本次上电程序首次运行的时间
        ret = proc_state_set(FILE_TMP_IDX, KEY_RUN_FIRST_TIME, (void *)curr.tv_sec);
        if (ret < 0)
        {
            return -1;
        }
    }
    else // 文件已创建
    {
        // 文件存在则加载文件
        ret = proc_state_load_file(FILE_TMP_IDX);
        if (ret < 0)
        {
            return -1;
        }

        // 本次上电程序异常数加1
        ret = proc_state_increase(FILE_TMP_IDX, KEY_RUN_EXCEPT, 1);
        if (ret < 0)
        {
            return -1;
        }
    }

    // 记录本次程序运行时间
    ret = proc_state_set(FILE_TMP_IDX, KEY_RUN_CURR_TIME, (void *)curr.tv_sec);
    if (ret < 0)
    {
        return -1;
    }
    // 记录本次时间统计的基点
    ret = proc_state_set(FILE_TMP_IDX, KEY_RUN_RECORD_TIME, (void *)curr.tv_sec);
    if (ret < 0)
    {
        return -1;
    }
    // 记录下程序的版本信息
    ret = proc_state_set(FILE_TMP_IDX, KEY_RUN_VERSION, (void *)version);
    if (ret < 0)
    {
        return -1;
    }

    // 再查询程序保存状态
    if (access(g_filenames[FILE_SAVE_IDX], R_OK) != 0)
    {
        // 程序首次在该设备运行，新建文件
        ret = proc_state_create_file(FILE_SAVE_IDX);
        if (ret < 0)
        {
            return -1;
        }
        ret = proc_state_set(FILE_SAVE_IDX, KEY_TOTAL_BOOTCNT, (void *)1);
        if (ret < 0)
        {
            return -1;
        }
    }
    else // 文件已创建
    {
        ret = proc_state_load_file(FILE_SAVE_IDX);
        if (ret < 0)
        {
            return -1;
        }
        // 本次上电运行的首次，则可以将总的启动次数加1
        if (g_tmp_states[KEY_RUN_EXCEPT].val_int == 0)
        {
            ret = proc_state_increase(FILE_SAVE_IDX, KEY_TOTAL_BOOTCNT, 1);
            if (ret < 0)
            {
                return -1;
            }
        }
        else // 本次上电运行的非首次，应该是发生异常，然后被重拉起来
        {
            ret = proc_state_increase(FILE_SAVE_IDX, KEY_TOTAL_EXCEPT, 1);
            if (ret < 0)
            {
                return -1;
            }
            ret = proc_state_set(FILE_SAVE_IDX, KEY_LAST_EXCEPT_VERSION, (void *)version);
            if (ret < 0)
            {
                return -1;
            }
            ret = proc_state_set(FILE_SAVE_IDX, KEY_LAST_EXCEPT_BOOTCNT, (void *)g_save_states[KEY_TOTAL_BOOTCNT].val_int);
            if (ret < 0)
            {
                return -1;
            }
            ret = proc_state_set(FILE_SAVE_IDX, KEY_LAST_EXCEPT_TOTAL, (void *)g_save_states[KEY_TOTAL_EXCEPT].val_int);
            if (ret < 0)
            {
                return -1;
            }
            ret = proc_state_set(FILE_SAVE_IDX, KEY_LAST_EXCEPT_TIME, (void *)curr.tv_sec);
            if (ret < 0)
            {
                return -1;
            }
        }
    }
    return 0;
}

// 可以定时调用(建议30分钟)
int proc_state_update(void)
{
    int last_sec, elapsed_sec, ret;
    struct timeval curr;
    gettimeofday(&curr, NULL);
    last_sec = g_tmp_states[KEY_RUN_RECORD_TIME].val_int;
    elapsed_sec = curr.tv_sec - last_sec;
    proc_state_increase(FILE_SAVE_IDX, KEY_TOTAL_SECOND, elapsed_sec);
    if (ret < 0)
    {
        return -1;
    }
    proc_state_increase(FILE_TMP_IDX, KEY_RUN_SECOND, elapsed_sec);
    if (ret < 0)
    {
        return -1;
    }
    proc_state_increase(FILE_TMP_IDX, KEY_RUN_TOTAL_SECOND, elapsed_sec);
    if (ret < 0)
    {
        return -1;
    }
    proc_state_set(FILE_TMP_IDX, KEY_RUN_RECORD_TIME, (void *)curr.tv_sec);
    if (ret < 0)
    {
        return -1;
    }
    return 0;
}

void *proc_state_query(file_e file_id, int key)
{

    if (file_id >= FILE_MAX)
    {
        return NULL;
    }
    switch (g_states[file_id][key].type)
    {
    case ITEM_TYPE_INT:
        return (void *)(g_states[file_id][key].val_int);
        break;
    case ITEM_TYPE_STR:
        return (void *)(g_states[file_id][key].val_str);
        break;
    }
    return NULL;
}

void proc_state_print(void)
{
    // 先打印本次运行临时状态，再打印出保存状态
    int i, j;
    state_item_t *p_state_items = NULL;
    struct timeval curr;

    // 更新运行时间
    gettimeofday(&curr, NULL);
    g_tmp_states[KEY_RUN_SECOND].val_int = (curr.tv_sec - g_tmp_states[KEY_RUN_CURR_TIME].val_int);
    g_tmp_states[KEY_RUN_TOTAL_SECOND].val_int = (curr.tv_sec - g_tmp_states[KEY_RUN_FIRST_TIME].val_int);

    for (i = 0; i < FILE_MAX; i++)
    {
        p_state_items = g_states[i];
        j = 0;
        do
        {
            switch (p_state_items[j].type)
            {
            case ITEM_TYPE_INT:
                g_print_func("process state : %-32s = %u\n", p_state_items[j].key, (unsigned int)p_state_items[j].val_int);
                break;
            case ITEM_TYPE_STR:
                g_print_func("process state : %-32s = %s\n", p_state_items[j].key, p_state_items[j].val_str);
                break;
            }
            j++;
        } while (p_state_items[j].type != ITEM_TYPE_END);
    }
    g_print_func("\n");
}
